//
// SoapTrace.Net - A soap message monitoring utility. 
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using SoapTrace.Core;
using SoapTrace.Core.Communication;
using SoapTrace.Core.Diagnostics;
using SoapTrace.Core.Formatters;
using System;
using System.Xml;

namespace SoapTrace
{	
	/// <summary>
	/// Manages the <see cref="SoapMessageMonitor"/> and the associated
	/// <see cref="SoapMessagesView"/>.
	/// </summary>
	public class MonitorManager
	{
		/// <summary>
		/// Matched soap response with a request.
		/// </summary>
		public event MatchedResponseEventHandler MatchedResponse;
		
		/// <summary>
		/// The soap message monitor.
		/// </summary>
		private SoapMessageMonitor m_SoapMessageMonitor =
			new SoapMessageMonitor( );
		
		/// <summary>
		/// The request and response messages collection.
		/// </summary>
		private SoapRequestAndResponseCollection
			m_SoapRequestAndResponses = new SoapRequestAndResponseCollection( );		
		
		/// <summary>
		/// The message replay manager.
		/// </summary>
		private SoapMessageReplayManager m_ReplayManager =
			new SoapMessageReplayManager( );
		
		/// <summary>
		/// The soap message view.
		/// </summary>
		private SoapMessagesView m_View;
		
		/// <summary>
		/// Message formatter manager.
		/// </summary>
		private MessageFormatterManager m_FormatterManager;	
		
		/// <summary>
		/// Manager Config.
		/// </summary>
		private MonitorManagerConfiguration m_Config
			= new MonitorManagerConfiguration( );
		
		/// <summary>
		/// Flag indicating whether the manager is configured.
		/// </summary>
		private bool m_IsConfigured;
				
		/// <summary>
		/// Creates a new instance of the <see cref="MonitorManager"/>
		/// class.
		/// </summary>
		public MonitorManager( SoapMessagesView view, 
		                      MessageFormatterManager formatterManager )
		{
			m_View = view;
			m_View.Manager = this;
			m_FormatterManager = formatterManager;
			
			m_SoapMessageMonitor.MissedConnectionCaptureEnabled = true;
			
   			// Hook up replay manager events.
   			m_ReplayManager.SoapRequestMessageReceived += 
   				new SoapRequestMessageReceivedEventHandler( ReplaySoapRequestReceived );

   			m_ReplayManager.SoapResponseMessageReceived += 
   				new SoapResponseMessageReceivedEventHandler( ReplaySoapResponseReceived );			
	
   			// Hook up soap message monitor events.
   			m_SoapMessageMonitor.SoapRequestMessageReceived += 
				new SoapRequestMessageReceivedEventHandler( OnSoapRequestReceived );

			m_SoapMessageMonitor.SoapResponseMessageReceived += 
				new SoapResponseMessageReceivedEventHandler( OnSoapResponseReceived );
		}
		
		/// <summary>
		/// Gets the soap message monitor associated with this
		/// manager.
		/// </summary>
		public SoapMessageMonitor Monitor
		{
			get
			{
				return m_SoapMessageMonitor;
			}
		}
		
		/// <summary>
		/// Gets the soap messages view associated with this
		/// manager.
		/// </summary>
		public SoapMessagesView View
		{
			get
			{
				return m_View;
			}
		}		
		
		/// <summary>
		/// Gets the configuration.
		/// </summary>
		public MonitorManagerConfiguration Config
		{
			get
			{
				return GetConfig( );
			}
			
			set
			{
				m_Config = value;
				Init( m_Config );
			}
		}
		
		/// <summary>
		/// Gets or sets whether the manager is configured.
		/// </summary>
		public bool IsConfigured
		{
			get
			{
				return m_IsConfigured;
			}
			
			set
			{
				m_IsConfigured = value;
			}
		}
	
		/// <summary>
		/// Starts up the monitor.
		/// </summary>
		public void Start( )
		{
			m_SoapMessageMonitor.Start( );
		}
		
		/// <summary>
		/// Stops the monitor manager.
		/// </summary>
		public void Stop( )
		{
			m_SoapMessageMonitor.Stop( );
			m_ReplayManager.Shutdown( );
		}

		/// <summary>
		/// Clears all the soap request and responses
		/// on display.</summary>
		public void Clear( )
		{
			lock( m_SoapRequestAndResponses.SyncRoot )
			{
				m_SoapRequestAndResponses.Clear( );
			}	
		}
		
		/// <summary>
		/// A <see cref="SoapRequestAndResponse"/> item has been selected 
		/// by the user.
		/// </summary>
		/// <param name="item">The item selected.</param>
		public void ItemSelected( SoapRequestAndResponse item )
		{
			if( item != null )
			{
				lock( m_SoapRequestAndResponses.SyncRoot )
				{
					DisplayItem( item );
				}
			}
		}	
		
		/// <summary>
		/// Redisplays the item selected.
		/// </summary>
		public void RedisplayItemSelected( )
		{
			lock( m_SoapRequestAndResponses.SyncRoot )
			{
				DisplayItem( m_View.SelectedItem );
			}			
		}
		
		/// <summary>
		/// Replays the selected item.
		/// </summary>
		public void Replay( )
		{
			if( m_View.RequestTextChanged )
			{
				if( m_FormatterManager.ShowSoapHeaders )
				{
					m_ReplayManager.Replay( m_View.RequestText.Replace( "\n", "\r\n" ),
					                        m_SoapMessageMonitor.RemoteServerIPAddress,
					                        m_SoapMessageMonitor.RemoteServerPort );
				}
				else
				{
					m_ReplayManager.Replay( m_View.SelectedItem.Request.Header,
					                        m_View.RequestText.Replace( "\n", "\r\n" ),
					                        m_SoapMessageMonitor.RemoteServerIPAddress,
					                        m_SoapMessageMonitor.RemoteServerPort );
					                        
				}
			}
			else
			{
				m_ReplayManager.Replay( m_View.SelectedItem.Request,
				                        m_SoapMessageMonitor.RemoteServerIPAddress,
				                        m_SoapMessageMonitor.RemoteServerPort );
			}
		}
		
		/// <summary>
		/// Raises the <see cref="MatchedResponse"/> event.
		/// </summary>
		/// <param name="item"></param>
		protected void OnMatchedResponse( SoapRequestAndResponse item )
		{
			if( MatchedResponse != null )
			{
				MatchedResponseEventArgs EventArgs = new MatchedResponseEventArgs( );
				EventArgs.Item = item;
				MatchedResponse( this, EventArgs );
			}
		}
		
		/// <summary>
		/// Soap message request received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapRequestReceivedEventArgs"/> object.</param>
		private void OnSoapRequestReceived( object sender, SoapRequestReceivedEventArgs e )
		{
			if( !m_ReplayManager.IsFromReplayClient( e.Request.Source ) )
			{
				ProcessSoapRequestReceived( e.ClientId, e.Request );
			}
			else
			{
				Logger.LogDebug( "Ignoring request sent by replay client." );
			}
		}

		/// <summary>
		/// Soap message response received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapResponseReceivedEventArgs"/> object.</param>
		/// <remarks>This method tries to match up the response with the
		/// corresponding request.  The matching algorithm is simple and
		/// just looks for the first request with the same client id
		/// that does not have a corresponding response.   This algorithm
		/// will have to be improved to handle the case where the
		/// server does not reply to the client.</remarks>
		private void OnSoapResponseReceived( object sender, SoapResponseReceivedEventArgs e )
		{
			if( !m_ReplayManager.IsFromReplayClient( e.Response.Source ) )
			{
				ProcessSoapResponseReceived( e.ClientId, e.Response );
			}
			else
			{
				Logger.LogDebug( "Ignoring response sent by replay client." );
			}
		}

		/// <summary>
		/// Appends a new soap request/response item to the messages list view.
		/// </summary>
		/// <param name="item">A soap request and response pair.</param>
		private void UpdateMessagesListView( SoapRequestAndResponse item )
		{
			m_View.AppendItem( item );
		}
		
		/// <summary>
		/// Display the soap request and response in the main form based on 
		/// the currently set formatting options.
		/// </summary>
		/// <param name="item">A soap request and response pair.</param>
		private void DisplayItem( SoapRequestAndResponse item )
		{
			if( item != null )
			{
				// Display the request

				DisplaySoapRequest( m_SoapMessageMonitor.RemoteServerIPAddress,
					item.Uri,
					item.WebService, 
					item.WebMethod, 
					item.Request );

                    

				// Format the response.

				DisplaySoapResponse( m_SoapMessageMonitor.RemoteServerIPAddress,
					item.Uri,
					item.WebService,
					item.WebMethod,
					item.Response );
			}
		}	
		
		/// <summary>
		/// Displays the currently selected soap message response using the
		/// currently selected formatter.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The web service uri.</param>
		/// <param name="webService">The web service name.</param>
		/// <param name="webServiceOperation">The web service operation name.</param>
		/// <param name="response">The soap response.</param>
		private void DisplaySoapResponse( string server,
			string uri,
			string webService,
			string webServiceOperation, 
			SoapResponse response )
		{
			if( response != null )
			{				
				try
				{
					m_View.Response = m_FormatterManager.CurrentFormatter.FormatResponse( server,
						uri,
						webService,
						webServiceOperation, 
						response );

                 
				}
				catch( XmlException Ex )
				{
					m_View.ResponseText = String.Empty;
					
					Console.WriteLine( Ex.ToString( ) );
				}
			}
			else
			{
				m_View.ResponseText = String.Empty;
			}
		}

		/// <summary>
		/// Displays the currently selected soap message request and 
		/// formats it based on the currently selected formatter.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The web service uri.</param>
		/// <param name="webService">The web service name.</param>
		/// <param name="webServiceOperation">The web service operation name.</param>
		/// <param name="request">The soap request.</param>
		private void DisplaySoapRequest( string server,
			string uri,
			string webService,
			string webServiceOperation, 
			SoapRequest request )
		{
			if( request != null )
			{		
				try
				{
					m_View.Request = m_FormatterManager.CurrentFormatter.FormatRequest( server,
						uri,
						webService,
						webServiceOperation, 
						request );
				}
				catch( XmlException Ex )
				{
					m_View.RequestText = String.Empty;
					Console.WriteLine( Ex.ToString( ) );
				}
			}
			else
			{
				m_View.RequestText = String.Empty;
			}
		}
		
		/// <summary>
		/// Replayed soap message request received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapRequestReceivedEventArgs"/> object.</param>
		private void ReplaySoapRequestReceived( object sender, SoapRequestReceivedEventArgs e )
		{
			ProcessSoapRequestReceived( e.ClientId, e.Request );
		}
		
		/// <summary>
		/// Replayed soap message response received.
		/// </summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">A <see cref="SoapResponseReceivedEventArgs"/> object.</param>
		private void ReplaySoapResponseReceived( object sender, SoapResponseReceivedEventArgs e )
		{
			ProcessSoapResponseReceived( e.ClientId, e.Response );
		}		
		
		/// <summary>
		/// Processes the received soap request.
		/// </summary>
		/// <param name="id">The client id associated with this request.</param>
		/// <param name="request">The soap request.</param>
		private void ProcessSoapRequestReceived( ClientId id, SoapRequest request )
		{
			Logger.LogDebug( String.Concat( "SoapRequestReceived.  Bytes.Length=", request.MessageLength, ", Source=", request.Source ) );
			
			if( Logger.IsLevelEnabled( LogLevel.Debug ) )
			{
				Logger.LogDebug( String.Concat( "SoapRequest Message=", request.Message ) );
			}
			
			lock( m_SoapRequestAndResponses.SyncRoot )
			{
				SoapRequestAndResponse Item = new SoapRequestAndResponse( id, request );
				m_SoapRequestAndResponses.Add( Item );

				UpdateMessagesListView( Item );
			}
		}
		
		/// <summary>
		/// Processes a soap response.
		/// </summary>
		/// <param name="id">The client id associated with this response.</param>
		/// <param name="response">The soap response.</param>
		private void ProcessSoapResponseReceived( ClientId id, SoapResponse response )
		{
			Logger.LogDebug( String.Concat( "SoapResponseReceived.  Bytes.Length=", response.MessageLength, ", Source=", response.Source ) );

			if( Logger.IsLevelEnabled( LogLevel.Debug ) )
			{
				Logger.LogDebug( String.Concat( "SoapResponse Message=", response.Message ) );
			}
			
			lock( m_SoapRequestAndResponses.SyncRoot )
			{
				SoapRequestAndResponse MatchedItem = 
					m_SoapRequestAndResponses.AddResponse( id, response );
				
				if( MatchedItem != null )
				{
					Logger.LogDebug( String.Concat( "Matched soap response.  Source=", response.Source ) );
				
					// Update UI if matching request is selected.
					
					if( m_View.SelectedItem == MatchedItem )
					{
						DisplayItem( MatchedItem );
					}
					
					OnMatchedResponse( MatchedItem );
				}
				else
				{
					Logger.LogWarning( String.Concat( "Could not match soap response.  Source=", response.Source ) );
					
					// Error.  Something's gone wrong, should not have a request
					// without a response.  Add the item anyway.
					
					SoapRequestAndResponse Item = new SoapRequestAndResponse( id );
					Item.Response = response;

					m_SoapRequestAndResponses.Add( Item );

					UpdateMessagesListView( Item );
				}
			}			
		}		
		
		/// <summary>
		/// Initialises the monitor.
		/// </summary>
		private void Init( MonitorManagerConfiguration config )
		{
			m_SoapMessageMonitor.UseProxyServer = config.UseProxyServer;
			m_SoapMessageMonitor.UseWinPcap = config.UseWinPcap;
			m_SoapMessageMonitor.UseRawSockets = config.UseRawSockets;
			m_SoapMessageMonitor.RemoteServerIPAddress = config.RemoteServerIPAddress;
			m_SoapMessageMonitor.RemoteServerPort = config.RemoteServerPort;
			m_SoapMessageMonitor.ProxyServerListeningPort = config.ProxyServerListeningPort;
			m_SoapMessageMonitor.NetworkInterfaceName = config.NetworkInterfaceName;
			m_SoapMessageMonitor.MissedConnectionCaptureEnabled = config.MissedConnectionCaptureEnabled;
					
			m_IsConfigured = true;
		}
		
		/// <summary>
		/// Gets the config for the monitor manager.
		/// </summary>
		/// <returns>A <see cref="MonitorManagerConfiguration"/>
		/// object.</returns>
		private MonitorManagerConfiguration GetConfig( )
		{
			m_Config.NetworkInterfaceName = m_SoapMessageMonitor.NetworkInterfaceName;
			m_Config.ProxyServerListeningPort = m_SoapMessageMonitor.ProxyServerListeningPort;
			m_Config.RemoteServerIPAddress = m_SoapMessageMonitor.RemoteServerIPAddress;
			m_Config.RemoteServerPort = m_SoapMessageMonitor.RemoteServerPort;
			m_Config.UseProxyServer = m_SoapMessageMonitor.UseProxyServer;
			m_Config.UseRawSockets = m_SoapMessageMonitor.UseRawSockets;
			m_Config.UseWinPcap = m_SoapMessageMonitor.UseWinPcap;
			
			return m_Config;
		}
	}
}
